package com.dba.example.guice.dao;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Singleton;

import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;

import com.dba.example.entities.Company;
import com.dba.example.guice.beans.OrderBy;
import com.dba.example.guice.beans.OrderBy.Direction;
import com.dba.example.guice.beans.OrderByComparator;
import com.dba.example.guice.beans.Page;
import com.dba.example.guice.beans.PageRequest;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Maps;

@Singleton
public class CompanyDaoImpl implements CompanyDao {
	
	private static final OrderBy DEFAULT_ORDER_BY = new OrderBy("name", Direction.ASC);
	
	@SuppressWarnings("unchecked")
	private static final List<OrderByComparator<Company>> ORDER_BY_COMPARATORS = Arrays.asList(
			new OrderByComparator<Company>(new OrderBy("name", Direction.ASC), new Comparator<Company>() {
				@Override
				public int compare(Company o1, Company o2) {
					return o1.getName().compareToIgnoreCase(o2.getName());
				}
			}),
			new OrderByComparator<Company>(new OrderBy("name", Direction.DESC), new Comparator<Company>() {
				@Override
				public int compare(Company o1, Company o2) {
					return o1.getName().compareToIgnoreCase(o2.getName()) * -1;
				}
			}),
			new OrderByComparator<Company>(new OrderBy("created", Direction.ASC), new Comparator<Company>() {
				@Override
				public int compare(Company o1, Company o2) {
					return o1.getCreated().compareTo(o2.getCreated());
				}
			}),
			new OrderByComparator<Company>(new OrderBy("created", Direction.DESC), new Comparator<Company>() {
				@Override
				public int compare(Company o1, Company o2) {
					return o1.getCreated().compareTo(o2.getCreated()) * -1;
				}
			}),
			new OrderByComparator<Company>(new OrderBy("lastUpdated", Direction.ASC), new Comparator<Company>() {
				@Override
				public int compare(Company o1, Company o2) {
					return o1.getLastUpdated().compareTo(o2.getLastUpdated());
				}
			}),
			new OrderByComparator<Company>(new OrderBy("lastUpdated", Direction.DESC), new Comparator<Company>() {
				@Override
				public int compare(Company o1, Company o2) {
					return o1.getLastUpdated().compareTo(o2.getLastUpdated()) * -1;
				}
			})
		);
	
	private static final Map<OrderBy, OrderByComparator<Company>> ORDER_BY_TO_COMPARATOR_MAP = Maps.uniqueIndex(ORDER_BY_COMPARATORS, new Function<OrderByComparator<Company>, OrderBy>() {
		public OrderBy apply(OrderByComparator<Company> input) {
			return input.getOrderBy();
		}
	});
	
	private List<Company> companies;
	
	private Map<Integer, Company> companyMap;
	
	private int nextId = 0;
	
	private Map<String, Comparator<Company>> comparators;
	
	public CompanyDaoImpl() {
		createData();
		createComparators();
	}
	
	private void createData() {
		companies = new ArrayList<Company>();
		for (int i = 1; i<=200; i++) {
			save(new Company(null, "Company " + i));
		}
	}
	
	private void createComparators() {
		comparators = new HashMap<String, Comparator<Company>>();
		
		
		comparators.put(new OrderBy("lastUpdated", Direction.ASC).toString().toLowerCase(), new Comparator<Company>() {
			@Override
			public int compare(Company o1, Company o2) {
				return o1.getLastUpdated().compareTo(o2.getLastUpdated());
			}
		});
		comparators.put(new OrderBy("lastUpdated", Direction.DESC).toString().toLowerCase(), new Comparator<Company>() {
			@Override
			public int compare(Company o1, Company o2) {
				return o1.getLastUpdated().compareTo(o2.getLastUpdated()) * -1;
			}
		});
	}
	
	@Override
	public List<Company> list() {
		return companies;
	}
	
	@Override
	public Page<Company> page(PageRequest request, String search) {
		List<Company> tmp = new ArrayList<Company>();
		if (StringUtils.isEmpty(search)) {
			tmp.addAll(companies);
		} else {
			final String searchLower = search.toLowerCase();
			
			tmp.addAll(Collections2.filter(companies, new Predicate<Company>() {
				public boolean apply(Company company) {
					return company.getName().toLowerCase().indexOf(searchLower) > -1;
				}
			}));
		}

		Comparator<Company> comparator = ORDER_BY_TO_COMPARATOR_MAP.get(DEFAULT_ORDER_BY).getComparator();
		if (request.getSort() != null && !request.getSort().isEmpty() && ORDER_BY_TO_COMPARATOR_MAP.containsKey(request.getSort().get(0))) {
			comparator = ORDER_BY_TO_COMPARATOR_MAP.get(request.getSort().get(0)).getComparator();
		}
		
		Collections.sort(tmp, comparator);
		
		return Page.fromList(tmp, request.getOffset(), request.getSize());
	}
	
	@Override
	public Company get(Integer id) {
		return companyMap.get(id);
	}
	
	@Override
	public Company save(Company company) {
		Company result = null;
		
		if (company.getId() == null) {
			result = create(company);
		} else {
			result = update(company);
		}
		updateMap();
		return result;
	}
	
	private Company create(Company company) {
		company.setId(++nextId);
		company.setCreated(new DateTime());
		company.setLastUpdated(new DateTime());
		companies.add(company);
		
		return company;
	}
	
	private Company update(Company company) {
		Company intCompany = companyMap.get(company.getId());
		intCompany.setName(company.getName());
		intCompany.setLastUpdated(new DateTime());
		return intCompany;
	}
	
	@Override
	public boolean delete(Integer id) {
		if (!companyMap.containsKey(id)) {
			return false;
		}
		
		return companies.remove(companyMap.get(id));
	}
	
	private void updateMap() {
		companyMap = Maps.uniqueIndex(companies, new Function<Company, Integer>() {
			public Integer apply(Company input) {
				return input.getId();
			}
		});
	}

}
